package com.blue.core.excel;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.blue.core.util.DateUtil;
import com.blue.core.util.MoneyUtil;

/**
 * Excel导入
 *
 * @author zhengjin
 * @since 1.0 2017年12月26日
 */
public class ExcelImporter
{
	private static Logger logger = LoggerFactory.getLogger(ExcelImporter.class);

	public ExcelImporter()
	{
	}

	/**
	 * Excel导入
	 * @param clazz 对象类型
	 * @param file excel文件
	 * @param header 是否跳过表头
	 * @return 导入为对象列表
	 */
	public static <T> List<T> input(Class<T> clazz, File file, boolean header) throws IOException
	{
		Assert.notNull(file, "文件不能为空");
		try (FileInputStream fis = new FileInputStream(file))
		{
			return input(clazz, fis, file.getName(), header);
		}
	}

	/**
	 * Excel导入
	 * @param clazz 对象类型
	 * @param file excel文件
	 * @param header 是否跳过表头
	 * @param  f 处理对象列表
	 */
	public static <T> void input(Class<T> clazz, File file, boolean header, Consumer<List<T>> f) throws IOException
	{
		Assert.notNull(file, "文件不能为空");
		try (FileInputStream fis = new FileInputStream(file))
		{
			input(clazz, fis, file.getName(), header, f);
		}
	}

	/**
	 * Excel导入
	 * @param clazz 对象类型
	 * @param is 输入流
	 * @param filename 文件名
	 * @param header 是否跳过表头
	 * @return 导入为对象列表
	 */
	public static <T> List<T> input(Class<T> clazz, InputStream is, String filename, boolean header) throws IOException
	{
		Assert.notNull(clazz, "对象类型不能为空");
		Workbook wb = createWorkbook(filename, is);

		ExcelConfig config = ExcelParser.getInstance().parse(clazz);
		Map<Integer, ExcelColumnConfig> columnMap = config.getColumnMap();
		List<T> list = new ArrayList<>();

		Sheet sheet = wb.getSheetAt(0);
		for (int i = (header) ? 1 : 0; i <= sheet.getLastRowNum(); i++)
		{
			Row row = sheet.getRow(i);
			if (row == null)
				continue;

			T object = createCellValue(row, clazz, columnMap);
			list.add(object);
		}

		return list;
	}

	/**
	 * Excel导入
	 * @param clazz 对象类型
	 * @param is 输入流
	 * @param filename 文件名
	 * @param header 是否跳过表头
	 * @param f 处理对象列表
	 */
	public static <T> void input(Class<T> clazz, InputStream is, String filename, boolean header, Consumer<List<T>> f) throws IOException
	{
		Assert.notNull(clazz, "对象类型不能为空");
		Workbook wb = createWorkbook(filename, is);

		ExcelConfig config = ExcelParser.getInstance().parse(clazz);
		Map<Integer, ExcelColumnConfig> columnMap = config.getColumnMap();
		List<T> list = new ArrayList<>();

		Sheet sheet = wb.getSheetAt(0);
		final int max = 20;
		for (int i = (header) ? 1 : 0; i <= sheet.getLastRowNum(); i++)
		{
			Row row = sheet.getRow(i);
			if (row == null)
				continue;

			T object = createCellValue(row, clazz, columnMap);
			list.add(object);

			if (list.size() >= max)
			{
				f.accept(list);
				list.clear();
			}
		}
		if (!list.isEmpty())
		{
			f.accept(list);
			list.clear();
		}
	}

	private static Workbook createWorkbook(String filename, InputStream is) throws IOException
	{
		Assert.notNull(is, "输入流不能为空");
		Assert.hasText(filename, "文件名不能为空");
		Workbook wb = null;
		if (filename.endsWith(".xls"))
		{
			wb = new HSSFWorkbook(is);
		}
		else if (filename.endsWith(".xlsx"))
		{
			wb = new XSSFWorkbook(is);
		}

		if (wb == null)
			throw new IllegalArgumentException("无法导入：" + filename);

		if (wb.getNumberOfSheets() != 1)
			throw new IllegalArgumentException("Excel文件只能有1个Sheet：" + filename);

		return wb;
	}

	private static <T> T createCellValue(Row row, Class<T> clazz, Map<Integer, ExcelColumnConfig> columnMap)
	{
		T object = null;
		try
		{
			object = clazz.newInstance();
		}
		catch (Exception e)
		{
			logger.error(e.getMessage(), e);
		}
		if (object == null)
			throw new IllegalArgumentException("无法实例化：" + clazz.getSimpleName());

		for (Map.Entry<Integer, ExcelColumnConfig> entry : columnMap.entrySet())
		{
			Cell cell = row.getCell(entry.getKey());
			if (cell == null)
				continue;

			Method setter = entry.getValue().getSetter();
			Class<?> param = setter.getParameterTypes()[0];
			try
			{
				Object cvtValue = getCellValue(cell, param, entry.getValue());
				//System.out.printf("%s - %s - %s - %s\n", setter.getName(), entry.getKey(), cvtValue, param.getSimpleName());
				setter.invoke(object, cvtValue);
			}
			catch (Exception e)
			{
				logger.error(e.getMessage(), e);
			}
		}
		return object;
	}

	private static Object getCellValue(Cell cell, Class<?> clazz, ExcelColumnConfig columnConfig)
	{
		CellType type = cell.getCellTypeEnum();
		if (clazz == String.class)
		{
			if (type == CellType.STRING || type == CellType.BLANK)
				return cell.getStringCellValue();
			else if (type == CellType.NUMERIC)
				return String.valueOf(cell.getNumericCellValue());
			else if (type == CellType.BOOLEAN)
				return String.valueOf(cell.getBooleanCellValue());
		}
		else if (clazz == Boolean.class || clazz == boolean.class)
		{
			if (type == CellType.BOOLEAN)
				return cell.getBooleanCellValue();
			else if (type == CellType.STRING)
				return Boolean.valueOf(cell.getStringCellValue());
			else if (type == CellType.NUMERIC)
				return MoneyUtil.compare(cell.getNumericCellValue(), 0) == 0 ? false : true;
		}
		else if (clazz == Double.class || clazz == double.class)
		{
			if (type == CellType.NUMERIC)
				return cell.getNumericCellValue();
			else if (type == CellType.STRING)
				return Double.valueOf(cell.getStringCellValue());
		}
		else if (clazz == Float.class || clazz == float.class)
		{
			if (type == CellType.NUMERIC)
				return (float)cell.getNumericCellValue();
			else if (type == CellType.STRING)
				return Float.valueOf(cell.getStringCellValue());
		}
		else if (clazz == Integer.class || clazz == int.class)
		{
			if (type == CellType.NUMERIC)
				return (int)cell.getNumericCellValue();
			else if (type == CellType.STRING)
				return Integer.valueOf(cell.getStringCellValue());
		}
		else if (clazz == Long.class || clazz == long.class)
		{
			if (type == CellType.NUMERIC)
				return (long)cell.getNumericCellValue();
			else if (type == CellType.STRING)
				return Long.valueOf(cell.getStringCellValue());
		}
		else if (clazz == Short.class || clazz == short.class)
		{
			if (type == CellType.NUMERIC)
				return (short)cell.getNumericCellValue();
			else if (type == CellType.STRING)
				return Short.valueOf(cell.getStringCellValue());
		}
		else if (clazz == Byte.class || clazz == byte.class)
		{
			if (type == CellType.NUMERIC)
				return (byte)cell.getNumericCellValue();
			else if (type == CellType.STRING)
				return Byte.valueOf(cell.getStringCellValue());
		}
		else if (clazz == Date.class)
		{
			if (type == CellType.NUMERIC)
				return cell.getDateCellValue();
			else if (type == CellType.STRING)
				return DateUtil.parse(cell.getStringCellValue(), columnConfig.getFormat());
		}
		else if (clazz == LocalDate.class)
		{
			if (type == CellType.NUMERIC)
				return cell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
			else if (type == CellType.STRING)
			{
				DateTimeFormatter formatter = DateTimeFormatter.ofPattern(columnConfig.getFormat());
				return LocalDate.parse(cell.getStringCellValue(), formatter);
			}
		}
		else if (clazz == LocalDateTime.class)
		{
			if (type == CellType.NUMERIC)
				return cell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
			else if (type == CellType.STRING)
			{
				DateTimeFormatter formatter = DateTimeFormatter.ofPattern(columnConfig.getFormat());
				return LocalDateTime.parse(cell.getStringCellValue(), formatter);
			}
		}

		return null;
	}

}
